home *** CD-ROM | disk | FTP | other *** search
Text File | 2010-07-21 | 85.9 KB | 2,118 lines |
- ######################################################################
- Log::Log4perl 1.29
- ######################################################################
-
- NAME
- Log::Log4perl - Log4j implementation for Perl
-
- SYNOPSIS
- # Easy mode if you like it simple ...
-
- use Log::Log4perl qw(:easy);
- Log::Log4perl->easy_init($ERROR);
-
- DEBUG "This doesn't go anywhere";
- ERROR "This gets logged";
-
- # ... or standard mode for more features:
-
- Log::Log4perl::init('/etc/log4perl.conf');
-
- --or--
-
- # Check config every 10 secs
- Log::Log4perl::init_and_watch('/etc/log4perl.conf',10);
-
- --then--
-
- $logger = Log::Log4perl->get_logger('house.bedrm.desk.topdrwr');
-
- $logger->debug('this is a debug message');
- $logger->info('this is an info message');
- $logger->warn('etc');
- $logger->error('..');
- $logger->fatal('..');
-
- #####/etc/log4perl.conf###############################
- log4perl.logger.house = WARN, FileAppndr1
- log4perl.logger.house.bedroom.desk = DEBUG, FileAppndr1
-
- log4perl.appender.FileAppndr1 = Log::Log4perl::Appender::File
- log4perl.appender.FileAppndr1.filename = desk.log
- log4perl.appender.FileAppndr1.layout = \
- Log::Log4perl::Layout::SimpleLayout
- ######################################################
-
- ABSTRACT
- Log::Log4perl provides a powerful logging API for your application
-
- DESCRIPTION
- Log::Log4perl lets you remote-control and fine-tune the logging
- behaviour of your system from the outside. It implements the widely
- popular (Java-based) Log4j logging package in pure Perl.
-
- For a detailed tutorial on Log::Log4perl usage, please read
-
- http://www.perl.com/pub/a/2002/09/11/log4perl.html
-
- Logging beats a debugger if you want to know what's going on in your
- code during runtime. However, traditional logging packages are too
- static and generate a flood of log messages in your log files that won't
- help you.
-
- "Log::Log4perl" is different. It allows you to control the number of
- logging messages generated at three different levels:
-
- * At a central location in your system (either in a configuration file
- or in the startup code) you specify *which components* (classes,
- functions) of your system should generate logs.
-
- * You specify how detailed the logging of these components should be
- by specifying logging *levels*.
-
- * You also specify which so-called *appenders* you want to feed your
- log messages to ("Print it to the screen and also append it to
- /tmp/my.log") and which format ("Write the date first, then the file
- name and line number, and then the log message") they should be in.
-
- This is a very powerful and flexible mechanism. You can turn on and off
- your logs at any time, specify the level of detail and make that
- dependent on the subsystem that's currently executed.
-
- Let me give you an example: You might find out that your system has a
- problem in the "MySystem::Helpers::ScanDir" component. Turning on
- detailed debugging logs all over the system would generate a flood of
- useless log messages and bog your system down beyond recognition. With
- "Log::Log4perl", however, you can tell the system: "Continue to log only
- severe errors to the log file. Open a second log file, turn on full
- debug logs in the "MySystem::Helpers::ScanDir" component and dump all
- messages originating from there into the new log file". And all this is
- possible by just changing the parameters in a configuration file, which
- your system can re-read even while it's running!
-
- How to use it
- The "Log::Log4perl" package can be initialized in two ways: Either via
- Perl commands or via a "log4j"-style configuration file.
-
- Initialize via a configuration file
- This is the easiest way to prepare your system for using
- "Log::Log4perl". Use a configuration file like this:
-
- ############################################################
- # A simple root logger with a Log::Log4perl::Appender::File
- # file appender in Perl.
- ############################################################
- log4perl.rootLogger=ERROR, LOGFILE
-
- log4perl.appender.LOGFILE=Log::Log4perl::Appender::File
- log4perl.appender.LOGFILE.filename=/var/log/myerrs.log
- log4perl.appender.LOGFILE.mode=append
-
- log4perl.appender.LOGFILE.layout=PatternLayout
- log4perl.appender.LOGFILE.layout.ConversionPattern=[%r] %F %L %c - %m%n
-
- These lines define your standard logger that's appending severe errors
- to "/var/log/myerrs.log", using the format
-
- [millisecs] source-filename line-number class - message newline
-
- Assuming that this configuration file is saved as "log.conf", you need
- to read it in in the startup section of your code, using the following
- commands:
-
- use Log::Log4perl;
- Log::Log4perl->init("log.conf");
-
- After that's done *somewhere* in the code, you can retrieve logger
- objects *anywhere* in the code. Note that there's no need to carry any
- logger references around with your functions and methods. You can get a
- logger anytime via a singleton mechanism:
-
- package My::MegaPackage;
- use Log::Log4perl;
-
- sub some_method {
- my($param) = @_;
-
- my $log = Log::Log4perl->get_logger("My::MegaPackage");
-
- $log->debug("Debug message");
- $log->info("Info message");
- $log->error("Error message");
-
- ...
- }
-
- With the configuration file above, "Log::Log4perl" will write "Error
- message" to the specified log file, but won't do anything for the
- "debug()" and "info()" calls, because the log level has been set to
- "ERROR" for all components in the first line of configuration file shown
- above.
-
- Why "Log::Log4perl->get_logger" and not "Log::Log4perl->new"? We don't
- want to create a new object every time. Usually in OO-Programming, you
- create an object once and use the reference to it to call its methods.
- However, this requires that you pass around the object to all functions
- and the last thing we want is pollute each and every function/method
- we're using with a handle to the "Logger":
-
- sub function { # Brrrr!!
- my($logger, $some, $other, $parameters) = @_;
- }
-
- Instead, if a function/method wants a reference to the logger, it just
- calls the Logger's static "get_logger($category)" method to obtain a
- reference to the *one and only* possible logger object of a certain
- category. That's called a *singleton* if you're a Gamma fan.
-
- How does the logger know which messages it is supposed to log and which
- ones to suppress? "Log::Log4perl" works with inheritance: The config
- file above didn't specify anything about "My::MegaPackage". And yet,
- we've defined a logger of the category "My::MegaPackage". In this case,
- "Log::Log4perl" will walk up the namespace hierarchy ("My" and then
- we're at the root) to figure out if a log level is defined somewhere. In
- the case above, the log level at the root (root *always* defines a log
- level, but not necessarily an appender) defines that the log level is
- supposed to be "ERROR" -- meaning that *DEBUG* and *INFO* messages are
- suppressed. Note that this 'inheritance' is unrelated to Perl's class
- inheritance, it is merely related to the logger namespace.
-
- Log Levels
- There are six predefined log levels: "FATAL", "ERROR", "WARN", "INFO",
- "DEBUG", and "TRACE" (in descending priority). Your configured logging
- level has to at least match the priority of the logging message.
-
- If your configured logging level is "WARN", then messages logged with
- "info()", "debug()", and "trace()" will be suppressed. "fatal()",
- "error()" and "warn()" will make their way through, because their
- priority is higher or equal than the configured setting.
-
- Instead of calling the methods
-
- $logger->trace("..."); # Log a trace message
- $logger->debug("..."); # Log a debug message
- $logger->info("..."); # Log a info message
- $logger->warn("..."); # Log a warn message
- $logger->error("..."); # Log a error message
- $logger->fatal("..."); # Log a fatal message
-
- you could also call the "log()" method with the appropriate level using
- the constants defined in "Log::Log4perl::Level":
-
- use Log::Log4perl::Level;
-
- $logger->log($TRACE, "...");
- $logger->log($DEBUG, "...");
- $logger->log($INFO, "...");
- $logger->log($WARN, "...");
- $logger->log($ERROR, "...");
- $logger->log($FATAL, "...");
-
- But nobody does that, really. Neither does anyone need more logging
- levels than these predefined ones. If you think you do, I would suggest
- you look into steering your logging behaviour via the category
- mechanism.
-
- If you need to find out if the currently configured logging level would
- allow a logger's logging statement to go through, use the logger's
- "is_*level*()" methods:
-
- $logger->is_trace() # True if trace messages would go through
- $logger->is_debug() # True if debug messages would go through
- $logger->is_info() # True if info messages would go through
- $logger->is_warn() # True if warn messages would go through
- $logger->is_error() # True if error messages would go through
- $logger->is_fatal() # True if fatal messages would go through
-
- Example: "$logger->is_warn()" returns true if the logger's current
- level, as derived from either the logger's category (or, in absence of
- that, one of the logger's parent's level setting) is $WARN, $ERROR or
- $FATAL.
-
- Also available are a series of more Java-esque functions which return
- the same values. These are of the format "is*Level*Enabled()", so
- "$logger->isDebugEnabled()" is synonymous to "$logger->is_debug()".
-
- These level checking functions will come in handy later, when we want to
- block unnecessary expensive parameter construction in case the logging
- level is too low to log the statement anyway, like in:
-
- if($logger->is_error()) {
- $logger->error("Erroneous array: @super_long_array");
- }
-
- If we had just written
-
- $logger->error("Erroneous array: @super_long_array");
-
- then Perl would have interpolated @super_long_array into the string via
- an expensive operation only to figure out shortly after that the string
- can be ignored entirely because the configured logging level is lower
- than $ERROR.
-
- The to-be-logged message passed to all of the functions described above
- can consist of an arbitrary number of arguments, which the logging
- functions just chain together to a single string. Therefore
-
- $logger->debug("Hello ", "World", "!"); # and
- $logger->debug("Hello World!");
-
- are identical.
-
- Note that even if one of the methods above returns true, it doesn't
- necessarily mean that the message will actually get logged. What
- is_debug() checks is that the logger used is configured to let a message
- of the given priority (DEBUG) through. But after this check, Log4perl
- will eventually apply custom filters and forward the message to one or
- more appenders. None of this gets checked by is_xxx(), for the simple
- reason that it's impossible to know what a custom filter does with a
- message without having the actual message or what an appender does to a
- message without actually having it log it.
-
- Log and die or warn
- Often, when you croak / carp / warn / die, you want to log those
- messages. Rather than doing the following:
-
- $logger->fatal($err) && die($err);
-
- you can use the following:
-
- $logger->logwarn();
- $logger->logdie();
-
- These print out log messages in the WARN and FATAL level, respectively,
- and then call the built-in warn() and die() functions. Since there is an
- ERROR level between WARN and FATAL, there are two additional helper
- functions in case you'd like to use ERROR for either warn() or die():
-
- $logger->error_warn();
- $logger->error_die();
-
- Finally, there's the Carp functions that do just what the Carp functions
- do, but with logging:
-
- $logger->logcarp(); # warn w/ 1-level stack trace
- $logger->logcluck(); # warn w/ full stack trace
- $logger->logcroak(); # die w/ 1-level stack trace
- $logger->logconfess(); # die w/ full stack trace
-
- Appenders
- If you don't define any appenders, nothing will happen. Appenders will
- be triggered whenever the configured logging level requires a message to
- be logged and not suppressed.
-
- "Log::Log4perl" doesn't define any appenders by default, not even the
- root logger has one.
-
- "Log::Log4perl" already comes with a standard set of appenders:
-
- Log::Log4perl::Appender::Screen
- Log::Log4perl::Appender::ScreenColoredLevels
- Log::Log4perl::Appender::File
- Log::Log4perl::Appender::Socket
- Log::Log4perl::Appender::DBI
- Log::Log4perl::Appender::Synchronized
- Log::Log4perl::Appender::RRDs
-
- to log to the screen, to files and to databases.
-
- On CPAN, you can find additional appenders like
-
- Log::Log4perl::Layout::XMLLayout
-
- by Guido Carls <gcarls@cpan.org>. It allows for hooking up Log::Log4perl
- with the graphical Log Analyzer Chainsaw (see "Can I use Log::Log4perl
- with log4j's Chainsaw?" in Log::Log4perl::FAQ).
-
- Additional Appenders via Log::Dispatch
- "Log::Log4perl" also supports *Dave Rolskys* excellent "Log::Dispatch"
- framework which implements a wide variety of different appenders.
-
- Here's the list of appender modules currently available via
- "Log::Dispatch":
-
- Log::Dispatch::ApacheLog
- Log::Dispatch::DBI (by Tatsuhiko Miyagawa)
- Log::Dispatch::Email,
- Log::Dispatch::Email::MailSend,
- Log::Dispatch::Email::MailSendmail,
- Log::Dispatch::Email::MIMELite
- Log::Dispatch::File
- Log::Dispatch::FileRotate (by Mark Pfeiffer)
- Log::Dispatch::Handle
- Log::Dispatch::Screen
- Log::Dispatch::Syslog
- Log::Dispatch::Tk (by Dominique Dumont)
-
- Please note that in order to use any of these additional appenders, you
- have to fetch Log::Dispatch from CPAN and install it. Also the
- particular appender you're using might require installing the particular
- module.
-
- For additional information on appenders, please check the
- Log::Log4perl::Appender manual page.
-
- Appender Example
- Now let's assume that we want to log "info()" or higher prioritized
- messages in the "Foo::Bar" category to both STDOUT and to a log file,
- say "test.log". In the initialization section of your system, just
- define two appenders using the readily available
- "Log::Log4perl::Appender::File" and "Log::Log4perl::Appender::Screen"
- modules:
-
- use Log::Log4perl;
-
- # Configuration in a string ...
- my $conf = q(
- log4perl.category.Foo.Bar = INFO, Logfile, Screen
-
- log4perl.appender.Logfile = Log::Log4perl::Appender::File
- log4perl.appender.Logfile.filename = test.log
- log4perl.appender.Logfile.layout = Log::Log4perl::Layout::PatternLayout
- log4perl.appender.Logfile.layout.ConversionPattern = [%r] %F %L %m%n
-
- log4perl.appender.Screen = Log::Log4perl::Appender::Screen
- log4perl.appender.Screen.stderr = 0
- log4perl.appender.Screen.layout = Log::Log4perl::Layout::SimpleLayout
- );
-
- # ... passed as a reference to init()
- Log::Log4perl::init( \$conf );
-
- Once the initialization shown above has happened once, typically in the
- startup code of your system, just use the defined logger anywhere in
- your system:
-
- ##########################
- # ... in some function ...
- ##########################
- my $log = Log::Log4perl::get_logger("Foo::Bar");
-
- # Logs both to STDOUT and to the file test.log
- $log->info("Important Info!");
-
- The "layout" settings specified in the configuration section define the
- format in which the message is going to be logged by the specified
- appender. The format shown for the file appender is logging not only the
- message but also the number of milliseconds since the program has
- started (%r), the name of the file the call to the logger has happened
- and the line number there (%F and %L), the message itself (%m) and a
- OS-specific newline character (%n):
-
- [187] ./myscript.pl 27 Important Info!
-
- The screen appender above, on the other hand, uses a "SimpleLayout",
- which logs the debug level, a hyphen (-) and the log message:
-
- INFO - Important Info!
-
- For more detailed info on layout formats, see "Log Layouts".
-
- In the configuration sample above, we chose to define a *category*
- logger ("Foo::Bar"). This will cause only messages originating from this
- specific category logger to be logged in the defined format and
- locations.
-
- Logging newlines
- There's some controversy between different logging systems as to when
- and where newlines are supposed to be added to logged messages.
-
- The Log4perl way is that a logging statement *should not* contain a
- newline:
-
- $logger->info("Some message");
- $logger->info("Another message");
-
- If this is supposed to end up in a log file like
-
- Some message
- Another message
-
- then an appropriate appender layout like "%m%n" will take care of adding
- a newline at the end of each message to make sure every message is
- printed on its own line.
-
- Other logging systems, Log::Dispatch in particular, recommend adding the
- newline to the log statement. This doesn't work well, however, if you,
- say, replace your file appender by a database appender, and all of a
- sudden those newlines scattered around the code don't make sense
- anymore.
-
- Assigning matching layouts to different appenders and leaving newlines
- out of the code solves this problem. If you inherited code that has
- logging statements with newlines and want to make it work with Log4perl,
- read the Log::Log4perl::Layout::PatternLayout documentation on how to
- accomplish that.
-
- Configuration files
- As shown above, you can define "Log::Log4perl" loggers both from within
- your Perl code or from configuration files. The latter have the
- unbeatable advantage that you can modify your system's logging behaviour
- without interfering with the code at all. So even if your code is being
- run by somebody who's totally oblivious to Perl, they still can adapt
- the module's logging behaviour to their needs.
-
- "Log::Log4perl" has been designed to understand "Log4j" configuration
- files -- as used by the original Java implementation. Instead of
- reiterating the format description in [2], let me just list three
- examples (also derived from [2]), which should also illustrate how it
- works:
-
- log4j.rootLogger=DEBUG, A1
- log4j.appender.A1=org.apache.log4j.ConsoleAppender
- log4j.appender.A1.layout=org.apache.log4j.PatternLayout
- log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c %x - %m%n
-
- This enables messages of priority "DEBUG" or higher in the root
- hierarchy and has the system write them to the console.
- "ConsoleAppender" is a Java appender, but "Log::Log4perl" jumps through
- a significant number of hoops internally to map these to their
- corresponding Perl classes, "Log::Log4perl::Appender::Screen" in this
- case.
-
- Second example:
-
- log4perl.rootLogger=DEBUG, A1
- log4perl.appender.A1=Log::Log4perl::Appender::Screen
- log4perl.appender.A1.layout=PatternLayout
- log4perl.appender.A1.layout.ConversionPattern=%d %-5p %c - %m%n
- log4perl.logger.com.foo=WARN
-
- This defines two loggers: The root logger and the "com.foo" logger. The
- root logger is easily triggered by debug-messages, but the "com.foo"
- logger makes sure that messages issued within the "Com::Foo" component
- and below are only forwarded to the appender if they're of priority
- *warning* or higher.
-
- Note that the "com.foo" logger doesn't define an appender. Therefore, it
- will just propagate the message up the hierarchy until the root logger
- picks it up and forwards it to the one and only appender of the root
- category, using the format defined for it.
-
- Third example:
-
- log4j.rootLogger=debug, stdout, R
- log4j.appender.stdout=org.apache.log4j.ConsoleAppender
- log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
- log4j.appender.stdout.layout.ConversionPattern=%5p (%F:%L) - %m%n
- log4j.appender.R=org.apache.log4j.RollingFileAppender
- log4j.appender.R.File=example.log
- log4j.appender.R.layout=org.apache.log4j.PatternLayout
- log4j.appender.R.layout.ConversionPattern=%p %c - %m%n
-
- The root logger defines two appenders here: "stdout", which uses
- "org.apache.log4j.ConsoleAppender" (ultimately mapped by "Log::Log4perl"
- to "Log::Log4perl::Appender::Screen") to write to the screen. And "R", a
- "org.apache.log4j.RollingFileAppender" (mapped by "Log::Log4perl" to
- "Log::Dispatch::FileRotate" with the "File" attribute specifying the log
- file.
-
- See Log::Log4perl::Config for more examples and syntax explanations.
-
- Log Layouts
- If the logging engine passes a message to an appender, because it thinks
- it should be logged, the appender doesn't just write it out haphazardly.
- There's ways to tell the appender how to format the message and add all
- sorts of interesting data to it: The date and time when the event
- happened, the file, the line number, the debug level of the logger and
- others.
-
- There's currently two layouts defined in "Log::Log4perl":
- "Log::Log4perl::Layout::SimpleLayout" and
- "Log::Log4perl::Layout::PatternLayout":
-
- "Log::Log4perl::SimpleLayout"
- formats a message in a simple way and just prepends it by the debug
- level and a hyphen: ""$level - $message", for example "FATAL - Can't
- open password file".
-
- "Log::Log4perl::Layout::PatternLayout"
- on the other hand is very powerful and allows for a very flexible
- format in "printf"-style. The format string can contain a number of
- placeholders which will be replaced by the logging engine when it's
- time to log the message:
-
- %c Category of the logging event.
- %C Fully qualified package (or class) name of the caller
- %d Current date in yyyy/MM/dd hh:mm:ss format
- %F File where the logging event occurred
- %H Hostname (if Sys::Hostname is available)
- %l Fully qualified name of the calling method followed by the
- callers source the file name and line number between
- parentheses.
- %L Line number within the file where the log statement was issued
- %m The message to be logged
- %m{chomp} The message to be logged, stripped off a trailing newline
- %M Method or function where the logging request was issued
- %n Newline (OS-independent)
- %p Priority of the logging event
- %P pid of the current process
- %r Number of milliseconds elapsed from program start to logging
- event
- %R Number of milliseconds elapsed from last logging event to
- current logging event
- %T A stack trace of functions called
- %x The topmost NDC (see below)
- %X{key} The entry 'key' of the MDC (see below)
- %% A literal percent (%) sign
-
- NDC and MDC are explained in "Nested Diagnostic Context (NDC)" and
- "Mapped Diagnostic Context (MDC)".
-
- Also, %d can be fine-tuned to display only certain characteristics
- of a date, according to the SimpleDateFormat in the Java World
- (http://java.sun.com/j2se/1.3/docs/api/java/text/SimpleDateFormat.ht
- ml)
-
- In this way, %d{HH:mm} displays only hours and minutes of the
- current date, while %d{yy, EEEE} displays a two-digit year, followed
- by a spelled-out (like "Wednesday").
-
- Similar options are available for shrinking the displayed category
- or limit file/path components, %F{1} only displays the source file
- *name* without any path components while %F logs the full path.
- %c{2} only logs the last two components of the current category,
- "Foo::Bar::Baz" becomes "Bar::Baz" and saves space.
-
- If those placeholders aren't enough, then you can define your own
- right in the config file like this:
-
- log4perl.PatternLayout.cspec.U = sub { return "UID $<" }
-
- See Log::Log4perl::Layout::PatternLayout for further details on
- customized specifiers.
-
- Please note that the subroutines you're defining in this way are
- going to be run in the "main" namespace, so be sure to fully qualify
- functions and variables if they're located in different packages.
-
- SECURITY NOTE: this feature means arbitrary perl code can be
- embedded in the config file. In the rare case where the people who
- have access to your config file are different from the people who
- write your code and shouldn't have execute rights, you might want to
- call
-
- Log::Log4perl::Config->allow_code(0);
-
- before you call init(). Alternatively you can supply a restricted
- set of Perl opcodes that can be embedded in the config file as
- described in "Restricting what Opcodes can be in a Perl Hook".
-
- All placeholders are quantifiable, just like in *printf*. Following this
- tradition, "%-20c" will reserve 20 chars for the category and
- left-justify it.
-
- For more details on logging and how to use the flexible and the simple
- format, check out the original "log4j" website under
-
- http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/SimpleLayout.html
- http://jakarta.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html
-
- Penalties
- Logging comes with a price tag. "Log::Log4perl" has been optimized to
- allow for maximum performance, both with logging enabled and disabled.
-
- But you need to be aware that there's a small hit every time your code
- encounters a log statement -- no matter if logging is enabled or not.
- "Log::Log4perl" has been designed to keep this so low that it will be
- unnoticable to most applications.
-
- Here's a couple of tricks which help "Log::Log4perl" to avoid
- unnecessary delays:
-
- You can save serious time if you're logging something like
-
- # Expensive in non-debug mode!
- for (@super_long_array) {
- $logger->debug("Element: $_");
- }
-
- and @super_long_array is fairly big, so looping through it is pretty
- expensive. Only you, the programmer, knows that going through that "for"
- loop can be skipped entirely if the current logging level for the actual
- component is higher than "debug". In this case, use this instead:
-
- # Cheap in non-debug mode!
- if($logger->is_debug()) {
- for (@super_long_array) {
- $logger->debug("Element: $_");
- }
- }
-
- If you're afraid that generating the parameters to the logging function
- is fairly expensive, use closures:
-
- # Passed as subroutine ref
- use Data::Dumper;
- $logger->debug(sub { Dumper($data) } );
-
- This won't unravel $data via Dumper() unless it's actually needed
- because it's logged.
-
- Also, Log::Log4perl lets you specify arguments to logger functions in
- *message output filter syntax*:
-
- $logger->debug("Structure: ",
- { filter => \&Dumper,
- value => $someref });
-
- In this way, shortly before Log::Log4perl sending the message out to any
- appenders, it will be searching all arguments for hash references and
- treat them in a special way:
-
- It will invoke the function given as a reference with the "filter" key
- ("Data::Dumper::Dumper()") and pass it the value that came with the key
- named "value" as an argument. The anonymous hash in the call above will
- be replaced by the return value of the filter function.
-
- Categories
- Categories are also called "Loggers" in Log4perl, both refer to the the
- same thing and these terms are used interchangeably. "Log::Log4perl"
- uses *categories* to determine if a log statement in a component should
- be executed or suppressed at the current logging level. Most of the
- time, these categories are just the classes the log statements are
- located in:
-
- package Candy::Twix;
-
- sub new {
- my $logger = Log::Log4perl->new("Candy::Twix");
- $logger->debug("Creating a new Twix bar");
- bless {}, shift;
- }
-
- # ...
-
- package Candy::Snickers;
-
- sub new {
- my $logger = Log::Log4perl->new("Candy.Snickers");
- $logger->debug("Creating a new Snickers bar");
- bless {}, shift;
- }
-
- # ...
-
- package main;
- Log::Log4perl->init("mylogdefs.conf");
-
- # => "LOG> Creating a new Snickers bar"
- my $first = Candy::Snickers->new();
- # => "LOG> Creating a new Twix bar"
- my $second = Candy::Twix->new();
-
- Note that you can separate your category hierarchy levels using either
- dots like in Java (.) or double-colons (::) like in Perl. Both notations
- are equivalent and are handled the same way internally.
-
- However, categories are just there to make use of inheritance: if you
- invoke a logger in a sub-category, it will bubble up the hierarchy and
- call the appropriate appenders. Internally, categories are not related
- to the class hierarchy of the program at all -- they're purely virtual.
- You can use arbitrary categories -- for example in the following
- program, which isn't oo-style, but procedural:
-
- sub print_portfolio {
-
- my $log = Log::Log4perl->new("user.portfolio");
- $log->debug("Quotes requested: @_");
-
- for(@_) {
- print "$_: ", get_quote($_), "\n";
- }
- }
-
- sub get_quote {
-
- my $log = Log::Log4perl->new("internet.quotesystem");
- $log->debug("Fetching quote: $_[0]");
-
- return yahoo_quote($_[0]);
- }
-
- The logger in first function, "print_portfolio", is assigned the
- (virtual) "user.portfolio" category. Depending on the "Log4perl"
- configuration, this will either call a "user.portfolio" appender, a
- "user" appender, or an appender assigned to root -- without
- "user.portfolio" having any relevance to the class system used in the
- program. The logger in the second function adheres to the
- "internet.quotesystem" category -- again, maybe because it's bundled
- with other Internet functions, but not because there would be a class of
- this name somewhere.
-
- However, be careful, don't go overboard: if you're developing a system
- in object-oriented style, using the class hierarchy is usually your best
- choice. Think about the people taking over your code one day: The class
- hierarchy is probably what they know right up front, so it's easy for
- them to tune the logging to their needs.
-
- Turn off a component
- "Log4perl" doesn't only allow you to selectively switch *on* a category
- of log messages, you can also use the mechanism to selectively *disable*
- logging in certain components whereas logging is kept turned on in
- higher-level categories. This mechanism comes in handy if you find that
- while bumping up the logging level of a high-level (i. e. close to root)
- category, that one component logs more than it should,
-
- Here's how it works:
-
- ############################################################
- # Turn off logging in a lower-level category while keeping
- # it active in higher-level categories.
- ############################################################
- log4perl.rootLogger=DEBUG, LOGFILE
- log4perl.logger.deep.down.the.hierarchy = ERROR, LOGFILE
-
- # ... Define appenders ...
-
- This way, log messages issued from within "Deep::Down::The::Hierarchy"
- and below will be logged only if they're "ERROR" or worse, while in all
- other system components even "DEBUG" messages will be logged.
-
- Return Values
- All logging methods return values indicating if their message actually
- reached one or more appenders. If the message has been suppressed
- because of level constraints, "undef" is returned.
-
- For example,
-
- my $ret = $logger->info("Message");
-
- will return "undef" if the system debug level for the current category
- is not "INFO" or more permissive. If Log::Log4perl forwarded the message
- to one or more appenders, the number of appenders is returned.
-
- If appenders decide to veto on the message with an appender threshold,
- the log method's return value will have them excluded. This means that
- if you've got one appender holding an appender threshold and you're
- logging a message which passes the system's log level hurdle but not the
- appender threshold, 0 will be returned by the log function.
-
- The bottom line is: Logging functions will return a *true* value if the
- message made it through to one or more appenders and a *false* value if
- it didn't. This allows for constructs like
-
- $logger->fatal("@_") or print STDERR "@_\n";
-
- which will ensure that the fatal message isn't lost if the current level
- is lower than FATAL or printed twice if the level is acceptable but an
- appender already points to STDERR.
-
- Pitfalls with Categories
- Be careful with just blindly reusing the system's packages as
- categories. If you do, you'll get into trouble with inherited methods.
- Imagine the following class setup:
-
- use Log::Log4perl;
-
- ###########################################
- package Bar;
- ###########################################
- sub new {
- my($class) = @_;
- my $logger = Log::Log4perl::get_logger(__PACKAGE__);
- $logger->debug("Creating instance");
- bless {}, $class;
- }
- ###########################################
- package Bar::Twix;
- ###########################################
- our @ISA = qw(Bar);
-
- ###########################################
- package main;
- ###########################################
- Log::Log4perl->init(\ qq{
- log4perl.category.Bar.Twix = DEBUG, Screen
- log4perl.appender.Screen = Log::Log4perl::Appender::Screen
- log4perl.appender.Screen.layout = SimpleLayout
- });
-
- my $bar = Bar::Twix->new();
-
- "Bar::Twix" just inherits everything from "Bar", including the
- constructor "new()". Contrary to what you might be thinking at first,
- this won't log anything. Reason for this is the "get_logger()" call in
- package "Bar", which will always get a logger of the "Bar" category,
- even if we call "new()" via the "Bar::Twix" package, which will make
- perl go up the inheritance tree to actually execute "Bar::new()". Since
- we've only defined logging behaviour for "Bar::Twix" in the
- configuration file, nothing will happen.
-
- This can be fixed by changing the "get_logger()" method in "Bar::new()"
- to obtain a logger of the category matching the *actual* class of the
- object, like in
-
- # ... in Bar::new() ...
- my $logger = Log::Log4perl::get_logger($class);
-
- This way, you'll make sure the logger logs appropriately, no matter if
- the method is inherited or called directly. "new()" always gets the real
- class name as an argument and all other methods can determine it via
- "ref($self)"), so it shouldn't be a problem to get the right class every
- time.
-
- Initialize once and only once
- It's important to realize that Log::Log4perl gets initialized once and
- only once, typically at the start of a program or system. Calling
- "init()" more than once will cause it to clobber the existing
- configuration and *replace* it by the new one.
-
- If you're in a traditional CGI environment, where every request is
- handeled by a new process, calling "init()" every time is fine. In
- persistent environments like "mod_perl", however, Log::Log4perl should
- be initialized either at system startup time (Apache offers startup
- handlers for that) or via
-
- # Init or skip if already done
- Log::Log4perl->init_once($conf_file);
-
- "init_once()" is identical to "init()", just with the exception that it
- will leave a potentially existing configuration alone and will only call
- "init()" if Log::Log4perl hasn't been initialized yet.
-
- If you're just curious if Log::Log4perl has been initialized yet, the
- check
-
- if(Log::Log4perl->initialized()) {
- # Yes, Log::Log4perl has already been initialized
- } else {
- # No, not initialized yet ...
- }
-
- can be used.
-
- If you're afraid that the components of your system are stepping on each
- other's toes or if you are thinking that different components should
- initialize Log::Log4perl seperately, try to consolidate your system to
- use a centralized Log4perl configuration file and use Log4perl's
- *categories* to separate your components.
-
- Custom Filters
- Log4perl allows the use of customized filters in its appenders to
- control the output of messages. These filters might grep for certain
- text chunks in a message, verify that its priority matches or exceeds a
- certain level or that this is the 10th time the same message has been
- submitted -- and come to a log/no log decision based upon these
- circumstantial facts.
-
- Check out Log::Log4perl::Filter for detailed instructions on how to use
- them.
-
- Performance
- The performance of Log::Log4perl calls obviously depends on a lot of
- things. But to give you a general idea, here's some rough numbers:
-
- On a Pentium 4 Linux box at 2.4 GHz, you'll get through
-
- * 500,000 suppressed log statements per second
-
- * 30,000 logged messages per second (using an in-memory appender)
-
- * init_and_watch delay mode: 300,000 suppressed, 30,000 logged.
- init_and_watch signal mode: 450,000 suppressed, 30,000 logged.
-
- Numbers depend on the complexity of the Log::Log4perl configuration. For
- a more detailed benchmark test, check the "docs/benchmark.results.txt"
- document in the Log::Log4perl distribution.
-
- Cool Tricks
- Here's a collection of useful tricks for the advanced "Log::Log4perl"
- user. For more, check the the FAQ, either in the distribution
- (Log::Log4perl::FAQ) or on http://log4perl.sourceforge.net.
-
- Shortcuts
- When getting an instance of a logger, instead of saying
-
- use Log::Log4perl;
- my $logger = Log::Log4perl->get_logger();
-
- it's often more convenient to import the "get_logger" method from
- "Log::Log4perl" into the current namespace:
-
- use Log::Log4perl qw(get_logger);
- my $logger = get_logger();
-
- Please note this difference: To obtain the root logger, please use
- "get_logger("")", call it without parameters ("get_logger()"), you'll
- get the logger of a category named after the current package.
- "get_logger()" is equivalent to "get_logger(__PACKAGE__)".
-
- Alternative initialization
- Instead of having "init()" read in a configuration file by specifying a
- file name or passing it a reference to an open filehandle
- ("Log::Log4perl->init( \*FILE )"), you can also pass in a reference to a
- string, containing the content of the file:
-
- Log::Log4perl->init( \$config_text );
-
- Also, if you've got the "name=value" pairs of the configuration in a
- hash, you can just as well initialize "Log::Log4perl" with a reference
- to it:
-
- my %key_value_pairs = (
- "log4perl.rootLogger" => "ERROR, LOGFILE",
- "log4perl.appender.LOGFILE" => "Log::Log4perl::Appender::File",
- ...
- );
-
- Log::Log4perl->init( \%key_value_pairs );
-
- Or also you can use a URL, see below:
-
- Using LWP to parse URLs
- (This section borrowed from XML::DOM::Parser by T.J. Mather).
-
- The init() function now also supports URLs, e.g.
- *http://www.erols.com/enno/xsa.xml*. It uses LWP to download the file
- and then calls parse() on the resulting string. By default it will use a
- LWP::UserAgent that is created as follows:
-
- use LWP::UserAgent;
- $LWP_USER_AGENT = LWP::UserAgent->new;
- $LWP_USER_AGENT->env_proxy;
-
- Note that env_proxy reads proxy settings from environment variables,
- which is what I need to do to get thru our firewall. If you want to use
- a different LWP::UserAgent, you can set it with
-
- Log::Log4perl::Config::set_LWP_UserAgent($my_agent);
-
- Currently, LWP is used when the filename (passed to parsefile) starts
- with one of the following URL schemes: http, https, ftp, wais, gopher,
- or file (followed by a colon.)
-
- Don't use this feature with init_and_watch().
-
- Automatic reloading of changed configuration files
- Instead of just statically initializing Log::Log4perl via
-
- Log::Log4perl->init($conf_file);
-
- there's a way to have Log::Log4perl periodically check for changes in
- the configuration and reload it if necessary:
-
- Log::Log4perl->init_and_watch($conf_file, $delay);
-
- In this mode, Log::Log4perl will examine the configuration file
- $conf_file every $delay seconds for changes via the file's last
- modification timestamp. If the file has been updated, it will be
- reloaded and replace the current Log::Log4perl configuration.
-
- The way this works is that with every logger function called (debug(),
- is_debug(), etc.), Log::Log4perl will check if the delay interval has
- expired. If so, it will run a -M file check on the configuration file.
- If its timestamp has been modified, the current configuration will be
- dumped and new content of the file will be loaded.
-
- This convenience comes at a price, though: Calling time() with every
- logging function call, especially the ones that are "suppressed" (!),
- will slow down these Log4perl calls by about 40%.
-
- To alleviate this performance hit a bit, "init_and_watch()" can be
- configured to listen for a Unix signal to reload the configuration
- instead:
-
- Log::Log4perl->init_and_watch($conf_file, 'HUP');
-
- This will set up a signal handler for SIGHUP and reload the
- configuration if the application receives this signal, e.g. via the
- "kill" command:
-
- kill -HUP pid
-
- where "pid" is the process ID of the application. This will bring you
- back to about 85% of Log::Log4perl's normal execution speed for
- suppressed statements. For details, check out "Performance". For more
- info on the signal handler, look for "SIGNAL MODE" in
- Log::Log4perl::Config::Watch.
-
- If you have a somewhat long delay set between physical config file
- checks or don't want to use the signal associated with the config file
- watcher, you can trigger a configuration reload at the next possible
- time by calling "Log::Log4perl::Config->watcher->force_next_check()".
-
- One thing to watch out for: If the configuration file contains a syntax
- or other fatal error, a running application will stop with "die" if this
- damaged configuration will be loaded during runtime, triggered either by
- a signal or if the delay period expired and the change is detected. This
- behaviour might change in the future.
-
- To allow the application to intercept and control a configuration reload
- in init_and_watch mode, a callback can be specified:
-
- Log::Log4perl->init_and_watch($conf_file, 10, {
- preinit_callback => \&callback });
-
- If Log4perl determines that the configuration needs to be reloaded, it
- will call the "preinit_callback" function without parameters. If the
- callback returns a true value, Log4perl will proceed and reload the
- configuration. If the callback returns a false value, Log4perl will keep
- the old configuration and skip reloading it until the next time around.
- Inside the callback, an application can run all kinds of checks,
- including accessing the configuration file, which is available via
- "Log::Log4perl::Config->watcher()->file()".
-
- Variable Substitution
- To avoid having to retype the same expressions over and over again,
- Log::Log4perl's configuration files support simple variable
- substitution. New variables are defined simply by adding
-
- varname = value
-
- lines to the configuration file before using
-
- ${varname}
-
- afterwards to recall the assigned values. Here's an example:
-
- layout_class = Log::Log4perl::Layout::PatternLayout
- layout_pattern = %d %F{1} %L> %m %n
-
- log4perl.category.Bar.Twix = WARN, Logfile, Screen
-
- log4perl.appender.Logfile = Log::Log4perl::Appender::File
- log4perl.appender.Logfile.filename = test.log
- log4perl.appender.Logfile.layout = ${layout_class}
- log4perl.appender.Logfile.layout.ConversionPattern = ${layout_pattern}
-
- log4perl.appender.Screen = Log::Log4perl::Appender::Screen
- log4perl.appender.Screen.layout = ${layout_class}
- log4perl.appender.Screen.layout.ConversionPattern = ${layout_pattern}
-
- This is a convenient way to define two appenders with the same layout
- without having to retype the pattern definitions.
-
- Variable substitution via "${varname}" will first try to find an
- explicitely defined variable. If that fails, it will check your shell's
- environment for a variable of that name. If that also fails, the program
- will "die()".
-
- Perl Hooks in the Configuration File
- If some of the values used in the Log4perl configuration file need to be
- dynamically modified by the program, use Perl hooks:
-
- log4perl.appender.File.filename = \
- sub { return getLogfileName(); }
-
- Each value starting with the string "sub {..." is interpreted as Perl
- code to be executed at the time the application parses the configuration
- via "Log::Log4perl::init()". The return value of the subroutine is used
- by Log::Log4perl as the configuration value.
-
- The Perl code is executed in the "main" package, functions in other
- packages have to be called in fully-qualified notation.
-
- Here's another example, utilizing an environment variable as a username
- for a DBI appender:
-
- log4perl.appender.DB.username = \
- sub { $ENV{DB_USER_NAME } }
-
- However, please note the difference between these code snippets and
- those used for user-defined conversion specifiers as discussed in
- Log::Log4perl::Layout::PatternLayout: While the snippets above are run
- *once* when "Log::Log4perl::init()" is called, the conversion specifier
- snippets are executed *each time* a message is rendered according to the
- PatternLayout.
-
- SECURITY NOTE: this feature means arbitrary perl code can be embedded in
- the config file. In the rare case where the people who have access to
- your config file are different from the people who write your code and
- shouldn't have execute rights, you might want to set
-
- Log::Log4perl::Config->allow_code(0);
-
- before you call init(). Alternatively you can supply a restricted set of
- Perl opcodes that can be embedded in the config file as described in
- "Restricting what Opcodes can be in a Perl Hook".
-
- Restricting what Opcodes can be in a Perl Hook
- The value you pass to Log::Log4perl::Config->allow_code() determines
- whether the code that is embedded in the config file is eval'd
- unrestricted, or eval'd in a Safe compartment. By default, a value of
- '1' is assumed, which does a normal 'eval' without any restrictions. A
- value of '0' however prevents any embedded code from being evaluated.
-
- If you would like fine-grained control over what can and cannot be
- included in embedded code, then please utilize the following methods:
-
- Log::Log4perl::Config->allow_code( $allow );
- Log::Log4perl::Config->allowed_code_ops($op1, $op2, ... );
- Log::Log4perl::Config->vars_shared_with_safe_compartment( [ \%vars | $package, \@vars ] );
- Log::Log4perl::Config->allowed_code_ops_convenience_map( [ \%map | $name, \@mask ] );
-
- Log::Log4perl::Config->allowed_code_ops() takes a list of opcode masks
- that are allowed to run in the compartment. The opcode masks must be
- specified as described in Opcode:
-
- Log::Log4perl::Config->allowed_code_ops(':subprocess');
-
- This example would allow Perl operations like backticks, system, fork,
- and waitpid to be executed in the compartment. Of course, you probably
- don't want to use this mask -- it would allow exactly what the Safe
- compartment is designed to prevent.
-
- Log::Log4perl::Config->vars_shared_with_safe_compartment() takes the
- symbols which should be exported into the Safe compartment before the
- code is evaluated. The keys of this hash are the package names that the
- symbols are in, and the values are array references to the literal
- symbol names. For convenience, the default settings export the '%ENV'
- hash from the 'main' package into the compartment:
-
- Log::Log4perl::Config->vars_shared_with_safe_compartment(
- main => [ '%ENV' ],
- );
-
- Log::Log4perl::Config->allowed_code_ops_convenience_map() is an accessor
- method to a map of convenience names to opcode masks. At present, the
- following convenience names are defined:
-
- safe = [ ':browse' ]
- restrictive = [ ':default' ]
-
- For convenience, if Log::Log4perl::Config->allow_code() is called with a
- value which is a key of the map previously defined with
- Log::Log4perl::Config->allowed_code_ops_convenience_map(), then the
- allowed opcodes are set according to the value defined in the map. If
- this is confusing, consider the following:
-
- use Log::Log4perl;
-
- my $config = <<'END';
- log4perl.logger = INFO, Main
- log4perl.appender.Main = Log::Log4perl::Appender::File
- log4perl.appender.Main.filename = \
- sub { "example" . getpwuid($<) . ".log" }
- log4perl.appender.Main.layout = Log::Log4perl::Layout::SimpleLayout
- END
-
- $Log::Log4perl::Config->allow_code('restrictive');
- Log::Log4perl->init( \$config ); # will fail
- $Log::Log4perl::Config->allow_code('safe');
- Log::Log4perl->init( \$config ); # will succeed
-
- The reason that the first call to ->init() fails is because the
- 'restrictive' name maps to an opcode mask of ':default'. getpwuid() is
- not part of ':default', so ->init() fails. The 'safe' name maps to an
- opcode mask of ':browse', which allows getpwuid() to run, so ->init()
- succeeds.
-
- allowed_code_ops_convenience_map() can be invoked in several ways:
-
- allowed_code_ops_convenience_map()
- Returns the entire convenience name map as a hash reference in
- scalar context or a hash in list context.
-
- allowed_code_ops_convenience_map( \%map )
- Replaces the entire conveniece name map with the supplied hash
- reference.
-
- allowed_code_ops_convenience_map( $name )
- Returns the opcode mask for the given convenience name, or undef if
- no such name is defined in the map.
-
- allowed_code_ops_convenience_map( $name, \@mask )
- Adds the given name/mask pair to the convenience name map. If the
- name already exists in the map, it's value is replaced with the new
- mask.
-
- as can vars_shared_with_safe_compartment():
-
- vars_shared_with_safe_compartment()
- Return the entire map of packages to variables as a hash reference
- in scalar context or a hash in list context.
-
- vars_shared_with_safe_compartment( \%packages )
- Replaces the entire map of packages to variables with the supplied
- hash reference.
-
- vars_shared_with_safe_compartment( $package )
- Returns the arrayref of variables to be shared for a specific
- package.
-
- vars_shared_with_safe_compartment( $package, \@vars )
- Adds the given package / varlist pair to the map. If the package
- already exists in the map, it's value is replaced with the new
- arrayref of variable names.
-
- For more information on opcodes and Safe Compartments, see Opcode and
- Safe.
-
- Changing the Log Level on a Logger
- Log4perl provides some internal functions for quickly adjusting the log
- level from within a running Perl program.
-
- Now, some people might argue that you should adjust your levels from
- within an external Log4perl configuration file, but Log4perl is
- everybody's darling.
-
- Typically run-time adjusting of levels is done at the beginning, or in
- response to some external input (like a "more logging" runtime command
- for diagnostics).
-
- You get the log level from a logger object with:
-
- $current_level = $logger->level();
-
- and you may set it with the same method, provided you first imported the
- log level constants, with:
-
- use Log::Log4perl::Level;
-
- Then you can set the level on a logger to one of the constants,
-
- $logger->level($ERROR); # one of DEBUG, INFO, WARN, ERROR, FATAL
-
- To increase the level of logging currently being done, use:
-
- $logger->more_logging($delta);
-
- and to decrease it, use:
-
- $logger->less_logging($delta);
-
- $delta must be a positive integer (for now, we may fix this later ;).
-
- There are also two equivalent functions:
-
- $logger->inc_level($delta);
- $logger->dec_level($delta);
-
- They're included to allow you a choice in readability. Some folks will
- prefer more/less_logging, as they're fairly clear in what they do, and
- allow the programmer not to worry too much about what a Level is and
- whether a higher Level means more or less logging. However, other folks
- who do understand and have lots of code that deals with levels will
- probably prefer the inc_level() and dec_level() methods as they want to
- work with Levels and not worry about whether that means more or less
- logging. :)
-
- That diatribe aside, typically you'll use more_logging() or inc_level()
- as such:
-
- my $v = 0; # default level of verbosity.
-
- GetOptions("v+" => \$v, ...);
-
- $logger->more_logging($v); # inc logging level once for each -v in ARGV
-
- Custom Log Levels
- First off, let me tell you that creating custom levels is heavily
- deprecated by the log4j folks. Indeed, instead of creating additional
- levels on top of the predefined DEBUG, INFO, WARN, ERROR and FATAL, you
- should use categories to control the amount of logging smartly, based on
- the location of the log-active code in the system.
-
- Nevertheless, Log4perl provides a nice way to create custom levels via
- the create_custom_level() routine function. However, this must be done
- before the first call to init() or get_logger(). Say you want to create
- a NOTIFY logging level that comes after WARN (and thus before INFO).
- You'd do such as follows:
-
- use Log::Log4perl;
- use Log::Log4perl::Level;
-
- Log::Log4perl::Logger::create_custom_level("NOTIFY", "WARN");
-
- And that's it! create_custom_level() creates the following functions /
- variables for level FOO:
-
- $FOO_INT # integer to use in L4p::Level::to_level()
- $logger->foo() # log function to log if level = FOO
- $logger->is_foo() # true if current level is >= FOO
-
- These levels can also be used in your config file, but note that your
- config file probably won't be portable to another log4perl or log4j
- environment unless you've made the appropriate mods there too.
-
- System-wide log levels
- As a fairly drastic measure to decrease (or increase) the logging level
- all over the system with one single configuration option, use the
- "threshold" keyword in the Log4perl configuration file:
-
- log4perl.threshold = ERROR
-
- sets the system-wide (or hierarchy-wide according to the log4j
- documentation) to ERROR and therefore deprives every logger in the
- system of the right to log lower-prio messages.
-
- Easy Mode
- For teaching purposes (especially for [1]), I've put ":easy" mode into
- "Log::Log4perl", which just initializes a single root logger with a
- defined priority and a screen appender including some nice standard
- layout:
-
- ### Initialization Section
- use Log::Log4perl qw(:easy);
- Log::Log4perl->easy_init($ERROR); # Set priority of root logger to ERROR
-
- ### Application Section
- my $logger = get_logger();
- $logger->fatal("This will get logged.");
- $logger->debug("This won't.");
-
- This will dump something like
-
- 2002/08/04 11:43:09 ERROR> script.pl:16 main::function - This will get logged.
-
- to the screen. While this has been proven to work well familiarizing
- people with "Log::Logperl" slowly, effectively avoiding to clobber them
- over the head with a plethora of different knobs to fiddle with
- (categories, appenders, levels, layout), the overall mission of
- "Log::Log4perl" is to let people use categories right from the start to
- get used to the concept. So, let's keep this one fairly hidden in the
- man page (congrats on reading this far :).
-
- Stealth loggers
- Sometimes, people are lazy. If you're whipping up a 50-line script and
- want the comfort of Log::Log4perl without having the burden of carrying
- a separate log4perl.conf file or a 5-liner defining that you want to
- append your log statements to a file, you can use the following
- features:
-
- use Log::Log4perl qw(:easy);
-
- Log::Log4perl->easy_init( { level => $DEBUG,
- file => ">>test.log" } );
-
- # Logs to test.log via stealth logger
- DEBUG("Debug this!");
- INFO("Info this!");
- WARN("Warn this!");
- ERROR("Error this!");
-
- some_function();
-
- sub some_function {
- # Same here
- FATAL("Fatal this!");
- }
-
- In ":easy" mode, "Log::Log4perl" will instantiate a *stealth logger*
- named $_default_logger and import it into the current package. Also, it
- will introduce the convenience functions "TRACE", "DEBUG()", "INFO()",
- "WARN()", "ERROR()", "FATAL()", and "ALWAYS" into the package namespace.
- These functions simply take messages as arguments and forward them to
- "_default_logger->debug()", "_default_logger->info()" and so on. If a
- message should never be blocked, regardless of the log level, use the
- "ALWAYS" function which corresponds to a log level of "OFF":
-
- ALWAYS "This will be printed regardless of the log level";
-
- The "easy_init" method can be called with a single level value to create
- a STDERR appender and a root logger as in
-
- Log::Log4perl->easy_init($DEBUG);
-
- or, as shown below (and in the example above) with a reference to a
- hash, specifying values for "level" (the logger's priority), "file" (the
- appender's data sink), "category" (the logger's category> and "layout"
- for the appender's pattern layout specification. All key-value pairs are
- optional, they default to $DEBUG for "level", "STDERR" for "file", ""
- (root category) for "category" and "%d %m%n" for "layout":
-
- Log::Log4perl->easy_init( { level => $DEBUG,
- file => ">test.log",
- utf8 => 1,
- category => "Bar::Twix",
- layout => '%F{1}-%L-%M: %m%n' } );
-
- The "file" parameter takes file names preceded by ">" (overwrite) and
- ">>" (append) as arguments. This will cause
- "Log::Log4perl::Appender::File" appenders to be created behind the
- scenes. Also the keywords "STDOUT" and "STDERR" (no ">" or ">>") are
- recognized, which will utilize and configure
- "Log::Log4perl::Appender::Screen" appropriately. The "utf8" flag, if set
- to a true value, runs a "binmode" command on the file handle to
- establish a utf8 line discpline on the file, otherwise you'll get a
- 'wide character in print' warning message and probably not what you'd
- expect as output.
-
- The stealth loggers can be used in different packages, you just need to
- make sure you're calling the "use" function in every package you're
- using "Log::Log4perl"'s easy services:
-
- package Bar::Twix;
- use Log::Log4perl qw(:easy);
- sub eat { DEBUG("Twix mjam"); }
-
- package Bar::Mars;
- use Log::Log4perl qw(:easy);
- sub eat { INFO("Mars mjam"); }
-
- package main;
-
- use Log::Log4perl qw(:easy);
-
- Log::Log4perl->easy_init( { level => $DEBUG,
- file => ">>test.log",
- category => "Bar::Twix",
- layout => '%F{1}-%L-%M: %m%n' },
- { level => $DEBUG,
- file => "STDOUT",
- category => "Bar::Mars",
- layout => '%m%n' },
- );
- Bar::Twix::eat();
- Bar::Mars::eat();
-
- As shown above, "easy_init()" will take any number of different logger
- definitions as hash references.
-
- Also, stealth loggers feature the functions "LOGWARN()", "LOGDIE()", and
- "LOGEXIT()", combining a logging request with a subsequent Perl warn()
- or die() or exit() statement. So, for example
-
- if($all_is_lost) {
- LOGDIE("Terrible Problem");
- }
-
- will log the message if the package's logger is at least "FATAL" but
- "die()" (including the traditional output to STDERR) in any case
- afterwards.
-
- See "Log and die or warn" for the similar "logdie()" and "logwarn()"
- functions of regular (i.e non-stealth) loggers.
-
- Similarily, "LOGCARP()", "LOGCLUCK()", "LOGCROAK()", and "LOGCONFESS()"
- are provided in ":easy" mode, facilitating the use of "logcarp()",
- "logcluck()", "logcroak()", and "logconfess()" with stealth loggers.
-
- When using Log::Log4perl in easy mode, please make sure you understand
- the implications of "Pitfalls with Categories".
-
- By the way, these convenience functions perform exactly as fast as the
- standard Log::Log4perl logger methods, there's *no* performance penalty
- whatsoever.
-
- Nested Diagnostic Context (NDC)
- If you find that your application could use a global (thread-specific)
- data stack which your loggers throughout the system have easy access to,
- use Nested Diagnostic Contexts (NDCs). Also check out "Mapped Diagnostic
- Context (MDC)", this might turn out to be even more useful.
-
- For example, when handling a request of a web client, it's probably
- useful to have the user's IP address available in all log statements
- within code dealing with this particular request. Instead of passing
- this piece of data around between your application functions, you can
- just use the global (but thread-specific) NDC mechanism. It allows you
- to push data pieces (scalars usually) onto its stack via
-
- Log::Log4perl::NDC->push("San");
- Log::Log4perl::NDC->push("Francisco");
-
- and have your loggers retrieve them again via the "%x" placeholder in
- the PatternLayout. With the stack values above and a PatternLayout
- format like "%x %m%n", the call
-
- $logger->debug("rocks");
-
- will end up as
-
- San Francisco rocks
-
- in the log appender.
-
- The stack mechanism allows for nested structures. Just make sure that at
- the end of the request, you either decrease the stack one by one by
- calling
-
- Log::Log4perl::NDC->pop();
- Log::Log4perl::NDC->pop();
-
- or clear out the entire NDC stack by calling
-
- Log::Log4perl::NDC->remove();
-
- Even if you should forget to do that, "Log::Log4perl" won't grow the
- stack indefinitely, but limit it to a maximum, defined in
- "Log::Log4perl::NDC" (currently 5). A call to "push()" on a full stack
- will just replace the topmost element by the new value.
-
- Again, the stack is always available via the "%x" placeholder in the
- Log::Log4perl::Layout::PatternLayout class whenever a logger fires. It
- will replace "%x" by the blank-separated list of the values on the
- stack. It does that by just calling
-
- Log::Log4perl::NDC->get();
-
- internally. See details on how this standard log4j feature is
- implemented in Log::Log4perl::NDC.
-
- Mapped Diagnostic Context (MDC)
- Just like the previously discussed NDC stores thread-specific
- information in a stack structure, the MDC implements a hash table to
- store key/value pairs in.
-
- The static method
-
- Log::Log4perl::MDC->put($key, $value);
-
- stores $value under a key $key, with which it can be retrieved later
- (possibly in a totally different part of the system) by calling the
- "get" method:
-
- my $value = Log::Log4perl::MDC->get($key);
-
- If no value has been stored previously under $key, the "get" method will
- return "undef".
-
- Typically, MDC values are retrieved later on via the "%X{...}"
- placeholder in "Log::Log4perl::Layout::PatternLayout". If the "get()"
- method returns "undef", the placeholder will expand to the string
- "[undef]".
-
- An application taking a web request might store the remote host like
-
- Log::Log4perl::MDC->put("remote_host", $r->headers("HOST"));
-
- at its beginning and if the appender's layout looks something like
-
- log4perl.appender.Logfile.layout.ConversionPattern = %X{remote_host}: %m%n
-
- then a log statement like
-
- DEBUG("Content delivered");
-
- will log something like
-
- adsl-63.dsl.snf.pacbell.net: Content delivered
-
- later on in the program.
-
- For details, please check Log::Log4perl::MDC.
-
- Resurrecting hidden Log4perl Statements
- Sometimes scripts need to be deployed in environments without having
- Log::Log4perl installed yet. On the other hand, you dont't want to live
- without your Log4perl statements -- they're gonna come in handy later.
-
- So, just deploy your script with Log4perl statements commented out with
- the pattern "###l4p", like in
-
- ###l4p DEBUG "It works!";
- # ...
- ###l4p INFO "Really!";
-
- If Log::Log4perl is available, use the ":resurrect" tag to have Log4perl
- resurrect those burried statements before the script starts running:
-
- use Log::Log4perl qw(:resurrect :easy);
-
- ###l4p Log::Log4perl->easy_init($DEBUG);
- ###l4p DEBUG "It works!";
- # ...
- ###l4p INFO "Really!";
-
- This will have a source filter kick in and indeed print
-
- 2004/11/18 22:08:46 It works!
- 2004/11/18 22:08:46 Really!
-
- In environments lacking Log::Log4perl, just comment out the first line
- and the script will run nevertheless (but of course without logging):
-
- # use Log::Log4perl qw(:resurrect :easy);
-
- ###l4p Log::Log4perl->easy_init($DEBUG);
- ###l4p DEBUG "It works!";
- # ...
- ###l4p INFO "Really!";
-
- because everything's a regular comment now. Alternatively, put the magic
- Log::Log4perl comment resurrection line into your shell's PERL5OPT
- environment variable, e.g. for bash:
-
- set PERL5OPT=-MLog::Log4perl=:resurrect,:easy
- export PERL5OPT
-
- This will awaken the giant within an otherwise silent script like the
- following:
-
- #!/usr/bin/perl
-
- ###l4p Log::Log4perl->easy_init($DEBUG);
- ###l4p DEBUG "It works!";
-
- As of "Log::Log4perl" 1.12, you can even force *all* modules loaded by a
- script to have their hidden Log4perl statements resurrected. For this to
- happen, load "Log::Log4perl::Resurrector" *before* loading any modules:
-
- use Log::Log4perl qw(:easy);
- use Log::Log4perl::Resurrector;
-
- use Foobar; # All hidden Log4perl statements in here will
- # be uncommented before Foobar gets loaded.
-
- Log::Log4perl->easy_init($DEBUG);
- ...
-
- Check the "Log::Log4perl::Resurrector" manpage for more details.
-
- Access defined appenders
- All appenders defined in the configuration file or via Perl code can be
- retrieved by the "appender_by_name()" class method. This comes in handy
- if you want to manipulate or query appender properties after the
- Log4perl configuration has been loaded via "init()".
-
- Note that internally, Log::Log4perl uses the "Log::Log4perl::Appender"
- wrapper class to control the real appenders (like
- "Log::Log4perl::Appender::File" or "Log::Dispatch::FileRotate"). The
- "Log::Log4perl::Appender" class has an "appender" attribute, pointing to
- the real appender.
-
- The reason for this is that external appenders like
- "Log::Dispatch::FileRotate" don't support all of Log::Log4perl's
- appender control mechanisms (like appender thresholds).
-
- The previously mentioned method "appender_by_name()" returns a reference
- to the *real* appender object. If you want access to the wrapper class
- (e.g. if you want to modify the appender's threshold), use the hash
- $Log::Log4perl::Logger::APPENDER_BY_NAME{...} instead, which holds
- references to all appender wrapper objects.
-
- Modify appender thresholds
- To conveniently adjust appender thresholds (e.g. because a script uses
- more_logging()), use
-
- # decrease thresholds of all appenders
- Log::Log4perl->appender_thresholds_adjust(-1);
-
- This will decrease the thresholds of all appenders in the system by one
- level, i.e. WARN becomes INFO, INFO becomes DEBUG, etc. To only modify
- selected ones, use
-
- # decrease thresholds of all appenders
- Log::Log4perl->appender_thresholds_adjust(-1, ['AppName1', ...]);
-
- and pass the names of affected appenders in a ref to an array.
-
- Advanced configuration within Perl
- Initializing Log::Log4perl can certainly also be done from within Perl.
- At last, this is what "Log::Log4perl::Config" does behind the scenes.
- Log::Log4perl's configuration file parsers are using a publically
- available API to set up Log::Log4perl's categories, appenders and
- layouts.
-
- Here's an example on how to configure two appenders with the same layout
- in Perl, without using a configuration file at all:
-
- ########################
- # Initialization section
- ########################
- use Log::Log4perl;
- use Log::Log4perl::Layout;
- use Log::Log4perl::Level;
-
- # Define a category logger
- my $log = Log::Log4perl->get_logger("Foo::Bar");
-
- # Define a layout
- my $layout = Log::Log4perl::Layout::PatternLayout->new("[%r] %F %L %m%n");
-
- # Define a file appender
- my $file_appender = Log::Log4perl::Appender->new(
- "Log::Log4perl::Appender::File",
- name => "filelog",
- filename => "/tmp/my.log");
-
- # Define a stdout appender
- my $stdout_appender = Log::Log4perl::Appender->new(
- "Log::Log4perl::Appender::Screen",
- name => "screenlog",
- stderr => 0);
-
- # Have both appenders use the same layout (could be different)
- $stdout_appender->layout($layout);
- $file_appender->layout($layout);
-
- $log->add_appender($stdout_appender);
- $log->add_appender($file_appender);
- $log->level($INFO);
-
- Please note the class of the appender object is passed as a *string* to
- "Log::Log4perl::Appender" in the *first* argument. Behind the scenes,
- "Log::Log4perl::Appender" will create the necessary
- "Log::Log4perl::Appender::*" (or "Log::Dispatch::*") object and pass
- along the name value pairs we provided to
- "Log::Log4perl::Appender->new()" after the first argument.
-
- The "name" value is optional and if you don't provide one,
- "Log::Log4perl::Appender->new()" will create a unique one for you. The
- names and values of additional parameters are dependent on the
- requirements of the particular appender class and can be looked up in
- their manual pages.
-
- A side note: In case you're wondering if
- "Log::Log4perl::Appender->new()" will also take care of the "min_level"
- argument to the "Log::Dispatch::*" constructors called behind the scenes
- -- yes, it does. This is because we want the "Log::Dispatch" objects to
- blindly log everything we send them ("debug" is their lowest setting)
- because *we* in "Log::Log4perl" want to call the shots and decide on
- when and what to log.
-
- The call to the appender's *layout()* method specifies the format (as a
- previously created "Log::Log4perl::Layout::PatternLayout" object) in
- which the message is being logged in the specified appender. If you
- don't specify a layout, the logger will fall back to
- "Log::Log4perl::SimpleLayout", which logs the debug level, a hyphen (-)
- and the log message.
-
- Layouts are objects, here's how you create them:
-
- # Create a simple layout
- my $simple = Log::Log4perl::SimpleLayout();
-
- # create a flexible layout:
- # ("yyyy/MM/dd hh:mm:ss (file:lineno)> message\n")
- my $pattern = Log::Log4perl::Layout::PatternLayout("%d (%F:%L)> %m%n");
-
- Every appender has exactly one layout assigned to it. You assign the
- layout to the appender using the appender's "layout()" object:
-
- my $app = Log::Log4perl::Appender->new(
- "Log::Log4perl::Appender::Screen",
- name => "screenlog",
- stderr => 0);
-
- # Assign the previously defined flexible layout
- $app->layout($pattern);
-
- # Add the appender to a previously defined logger
- $logger->add_appender($app);
-
- # ... and you're good to go!
- $logger->debug("Blah");
- # => "2002/07/10 23:55:35 (test.pl:207)> Blah\n"
-
- It's also possible to remove appenders from a logger:
-
- $logger->remove_appender($appender_name);
-
- will remove an appender, specified by name, from a given logger. Please
- note that this does *not* remove an appender from the system.
-
- To eradicate an appender from the system, you need to call
- "Log::Log4perl->eradicate_appender($appender_name)" which will first
- remove the appender from every logger in the system and then will delete
- all references Log4perl holds to it.
-
- How about Log::Dispatch::Config?
- Tatsuhiko Miyagawa's "Log::Dispatch::Config" is a very clever simplified
- logger implementation, covering some of the *log4j* functionality. Among
- the things that "Log::Log4perl" can but "Log::Dispatch::Config" can't
- are:
-
- * You can't assign categories to loggers. For small systems that's
- fine, but if you can't turn off and on detailed logging in only a
- tiny subsystem of your environment, you're missing out on a majorly
- useful log4j feature.
-
- * Defining appender thresholds. Important if you want to solve
- problems like "log all messages of level FATAL to STDERR, plus log
- all DEBUG messages in "Foo::Bar" to a log file". If you don't have
- appenders thresholds, there's no way to prevent cluttering STDERR
- with DEBUG messages.
-
- * PatternLayout specifications in accordance with the standard (e.g.
- "%d{HH:mm}").
-
- Bottom line: Log::Dispatch::Config is fine for small systems with simple
- logging requirements. However, if you're designing a system with lots of
- subsystems which you need to control independantly, you'll love the
- features of "Log::Log4perl", which is equally easy to use.
-
- Using Log::Log4perl with wrapper functions and classes
- If you don't use "Log::Log4perl" as described above, but from a wrapper
- function, the pattern layout will generate wrong data for %F, %C, %L,
- and the like. Reason for this is that "Log::Log4perl"'s loggers assume a
- static caller depth to the application that's using them.
-
- If you're using one (or more) wrapper functions, "Log::Log4perl" will
- indicate where your logger function called the loggers, not where your
- application called your wrapper:
-
- use Log::Log4perl qw(:easy);
- Log::Log4perl->easy_init({ level => $DEBUG,
- layout => "%M %m%n" });
-
- sub mylog {
- my($message) = @_;
-
- DEBUG $message;
- }
-
- sub func {
- mylog "Hello";
- }
-
- func();
-
- prints
-
- main::mylog Hello
-
- but that's probably not what your application expects. Rather, you'd
- want
-
- main::func Hello
-
- because the "func" function called your logging function.
-
- But don't dispair, there's a solution: Just register your wrapper
- package with Log4perl beforehand. If Log4perl then finds that it's being
- called from a registered wrapper, it will automatically step up to the
- next call frame.
-
- Log::Log4perl->wrapper_register(__PACKAGE__);
-
- sub mylog {
- my($message) = @_;
-
- DEBUG $message;
- }
-
- Alternatively, you can increase the value of the global variable
- $Log::Log4perl::caller_depth (defaults to 0) by one for every wrapper
- that's in between your application and "Log::Log4perl", then
- "Log::Log4perl" will compensate for the difference:
-
- sub mylog {
- my($message) = @_;
-
- local $Log::Log4perl::caller_depth =
- $Log::Log4perl::caller_depth + 1;
- DEBUG $message;
- }
-
- Also, note that if you're writing a subclass of Log4perl, like
-
- package MyL4pWrapper;
- use Log::Log4perl;
- our @ISA = qw(Log::Log4perl);
-
- and you want to call get_logger() in your code, like
-
- use MyL4pWrapper;
-
- sub get_logger {
- my $logger = Log::Log4perl->get_logger();
- }
-
- then the get_logger() call will get a logger for the "MyL4pWrapper"
- category, not for the package calling the wrapper class as in
-
- package UserPackage;
- my $logger = MyL4pWrapper->get_logger();
-
- To have the above call to get_logger return a logger for the
- "UserPackage" category, you need to tell Log4perl that "MyL4pWrapper" is
- a Log4perl wrapper class:
-
- use MyL4pWrapper;
- Log::Log4perl->wrapper_register(__PACKAGE__);
-
- sub get_logger {
- # Now gets a logger for the category of the calling package
- my $logger = Log::Log4perl->get_logger();
- }
-
- This feature works both for Log4perl-relaying classes like the wrapper
- described above, and for wrappers that inherit from Log4perl use
- Log4perl's get_logger function via inheritance, alike.
-
- Access to Internals
- The following methods are only of use if you want to peek/poke in the
- internals of Log::Log4perl. Be careful not to disrupt its inner
- workings.
-
- "Log::Log4perl->appenders()"
- To find out which appenders are currently defined (not only for a
- particular logger, but overall), a "appenders()" method is available
- to return a reference to a hash mapping appender names to their
- Log::Log4perl::Appender object references.
-
- Dirty Tricks
- infiltrate_lwp()
- The famous LWP::UserAgent module isn't Log::Log4perl-enabled. Often,
- though, especially when tracing Web-related problems, it would be
- helpful to get some insight on what's happening inside
- LWP::UserAgent. Ideally, LWP::UserAgent would even play along in the
- Log::Log4perl framework.
-
- A call to "Log::Log4perl->infiltrate_lwp()" does exactly this. In a
- very rude way, it pulls the rug from under LWP::UserAgent and
- transforms its "debug/conn" messages into "debug()" calls of loggers
- of the category "LWP::UserAgent". Similarily, "LWP::UserAgent"'s
- "trace" messages are turned into "Log::Log4perl"'s "info()" method
- calls. Note that this only works for LWP::UserAgent versions <
- 5.822, because this (and probably later) versions miss debugging
- functions entirely.
-
- Suppressing 'duplicate' LOGDIE messages
- If a script with a simple Log4perl configuration uses logdie() to
- catch errors and stop processing, as in
-
- use Log::Log4perl qw(:easy) ;
- Log::Log4perl->easy_init($DEBUG);
-
- shaky_function() or LOGDIE "It failed!";
-
- there's a cosmetic problem: The message gets printed twice:
-
- 2005/07/10 18:37:14 It failed!
- It failed! at ./t line 12
-
- The obvious solution is to use LOGEXIT() instead of LOGDIE(), but
- there's also a special tag for Log4perl that suppresses the second
- message:
-
- use Log::Log4perl qw(:no_extra_logdie_message);
-
- This causes logdie() and logcroak() to call exit() instead of die().
- To modify the script exit code in these occasions, set the variable
- $Log::Log4perl::LOGEXIT_CODE to the desired value, the default is 1.
-
- Redefine values without causing errors
- Log4perl's configuration file parser has a few basic safety
- mechanisms to make sure configurations are more or less sane.
-
- One of these safety measures is catching redefined values. For
- example, if you first write
-
- log4perl.category = WARN, Logfile
-
- and then a couple of lines later
-
- log4perl.category = TRACE, Logfile
-
- then you might have unintentionally overwritten the first value and
- Log4perl will die on this with an error (suspicious configurations
- always throw an error). Now, there's a chance that this is
- intentional, for example when you're lumping together several
- configuration files and actually *want* the first value to overwrite
- the second. In this case use
-
- use Log::Log4perl qw(:nostrict);
-
- to put Log4perl in a more permissive mode.
-
- EXAMPLE
- A simple example to cut-and-paste and get started:
-
- use Log::Log4perl qw(get_logger);
-
- my $conf = q(
- log4perl.category.Bar.Twix = WARN, Logfile
- log4perl.appender.Logfile = Log::Log4perl::Appender::File
- log4perl.appender.Logfile.filename = test.log
- log4perl.appender.Logfile.layout = \
- Log::Log4perl::Layout::PatternLayout
- log4perl.appender.Logfile.layout.ConversionPattern = %d %F{1} %L> %m %n
- );
-
- Log::Log4perl::init(\$conf);
-
- my $logger = get_logger("Bar::Twix");
- $logger->error("Blah");
-
- This will log something like
-
- 2002/09/19 23:48:15 t1 25> Blah
-
- to the log file "test.log", which Log4perl will append to or create it
- if it doesn't exist already.
-
- INSTALLATION
- If you want to use external appenders provided with "Log::Dispatch", you
- need to install "Log::Dispatch" (2.00 or better) from CPAN, which itself
- depends on "Attribute-Handlers" and "Params-Validate". And a lot of
- other modules, that's the reason why we're now shipping Log::Log4perl
- with its own standard appenders and only if you wish to use additional
- ones, you'll have to go through the "Log::Dispatch" installation
- process.
-
- Log::Log4perl needs "Test::More", "Test::Harness" and "File::Spec", but
- they already come with fairly recent versions of perl. If not,
- everything's automatically fetched from CPAN if you're using the CPAN
- shell (CPAN.pm), because they're listed as dependencies.
-
- "Time::HiRes" (1.20 or better) is required only if you need the
- fine-grained time stamps of the %r parameter in
- "Log::Log4perl::Layout::PatternLayout".
-
- Manual installation works as usual with
-
- perl Makefile.PL
- make
- make test
- make install
-
- If you're running Windows (98, 2000, NT, XP etc.), and you're too lazy
- to rummage through all of Log-Log4perl's dependencies, don't despair:
- We're providing a PPM package which installs easily with your
- Activestate Perl. Check
- "how_can_i_install_log__log4perl_on_microsoft_windows" in
- Log::Log4perl::FAQ for details.
-
- DEVELOPMENT
- Log::Log4perl is still being actively developed. We will always make
- sure the test suite (approx. 500 cases) will pass, but there might still
- be bugs. please check http://github.com/mschilli/log4perl for the latest
- release. The api has reached a mature state, we will not change it
- unless for a good reason.
-
- Bug reports and feedback are always welcome, just email them to our
- mailing list shown in the AUTHORS section. We're usually addressing them
- immediately.
-
- REFERENCES
- [1] Michael Schilli, "Retire your debugger, log smartly with
- Log::Log4perl!", Tutorial on perl.com, 09/2002,
- http://www.perl.com/pub/a/2002/09/11/log4perl.html
-
- [2] Ceki G├╝lc├╝, "Short introduction to log4j",
- http://jakarta.apache.org/log4j/docs/manual.html
-
- [3] Vipan Singla, "Don't Use System.out.println! Use Log4j.",
- http://www.vipan.com/htdocs/log4jhelp.html
-
- [4] The Log::Log4perl project home page: http://log4perl.com
-
- SEE ALSO
- Log::Log4perl::Config, Log::Log4perl::Appender,
- Log::Log4perl::Layout::PatternLayout,
- Log::Log4perl::Layout::SimpleLayout, Log::Log4perl::Level,
- Log::Log4perl::JavaMap Log::Log4perl::NDC,
-
- AUTHORS
- Please contribute patches to the project page on Github:
-
- http://github.com/mschilli/log4perl
-
- Bug reports or requests for enhancements to the authors via our
-
- MAILING LIST (questions, bug reports, suggestions/patches):
- log4perl-devel@lists.sourceforge.net
-
- Authors (please contact them via the list above, not directly)
- Mike Schilli <m@perlmeister.com>
- Kevin Goess <cpan@goess.org>
-
- Contributors (in alphabetical order):
- Ateeq Altaf, Cory Bennett, Jens Berthold, Jeremy Bopp, Hutton
- Davidson, Chris R. Donnelly, Matisse Enzer, Hugh Esco, Anthony
- Foiani, James FitzGibbon, Carl Franks, Dennis Gregorovic, Andy
- Grundman, Paul Harrington, David Hull, Robert Jacobson, Jason Kohles,
- Jeff Macdonald, Markus Peter, Brett Rann, Peter Rabbitson, Erik
- Selberg, Aaron Straup Cope, Lars Thegler, David Viner, Mac Yang.
-
- COPYRIGHT AND LICENSE
- Copyright 2002-2009 by Mike Schilli <m@perlmeister.com> and Kevin Goess
- <cpan@goess.org>.
-
- This library is free software; you can redistribute it and/or modify it
- under the same terms as Perl itself.
-
-